package org.dmikis.task11;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.RenderingHints;

import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;

import java.util.ArrayList;
import java.util.ListIterator;

import javax.swing.JPanel;

import org.dmikis.jmmengine.models.Result;

public class Task11PhasePoints implements Result {
    
    public Task11PhasePoints() {
	this.phaseSpace = new ArrayList<PhasePoint>();
	this.track = new ArrayList<Point2D.Double>();
	
	this.plotVT     = new ArrayList<Point2D.Double>();
	this.plotTettaT = new ArrayList<Point2D.Double>();
	this.plotTettaV = new ArrayList<Point2D.Double>();
	
	this.plotPanelVT = new PlotPanel("v(t)", this.plotVT, Color.red);
	this.plotPanelTettaT = new PlotPanel("tetta(t)", this.plotTettaT, Color.blue);
	this.plotPanelTettaV = new PlotPanel("tetta(v)", this.plotTettaV, Color.green);
	this.plotPanelTrack = new PlotPanel("track", this.track, Color.cyan);
    }
    
    public void clear() {
	this.phaseSpace.clear();
    }
    
    public void add(PhasePoint newPoint) {
	this.phaseSpace.add(newPoint);
    }
    
    public void updatePlots() {
	this.plotVT.clear();
	this.plotTettaT.clear();
	this.plotTettaV.clear();
	
	ListIterator<PhasePoint> phaseSpaceIterator = this.phaseSpace.listIterator();
	while (phaseSpaceIterator.hasNext()) {
	    PhasePoint currentPoint = phaseSpaceIterator.next();
	    this.plotVT.add(new Point2D.Double(currentPoint.getT(), currentPoint.getV()));
	    this.plotTettaT.add(new Point2D.Double(currentPoint.getT(), currentPoint.getTetta()));
	    this.plotTettaV.add(new Point2D.Double(currentPoint.getV(), currentPoint.getTetta()));
	}
	
	this.plotPanelVT.repaint();
	this.plotPanelTettaT.repaint();
	this.plotPanelTettaV.repaint();
	this.plotPanelTrack.repaint();
    }

    @Override
    public JPanel getVisualizationPanel() {
	JPanel rootPanel = new JPanel();
	rootPanel.setLayout(new GridLayout(2, 2));
	rootPanel.add(plotPanelVT);
	rootPanel.add(plotPanelTettaV);
	rootPanel.add(plotPanelTettaT);
	rootPanel.add(plotPanelTrack);
	return rootPanel;
    }

    @SuppressWarnings("serial")
    static class PlotPanel extends JPanel {

	public PlotPanel(String name, ArrayList<? extends Point2D.Double> data, Color color) {
	    super();
	    this.setMinimumSize(new Dimension(150, 150));
	    this.name = name;
	    this.data = data;
	    this.lineColor = color;
	}

	@Override
	public void paintComponent(Graphics g) {
	    g.clearRect(0, 0, this.getWidth(), this.getHeight());
	    Graphics2D g2 = (Graphics2D) g;
	    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
	    // Draw plot name
	    g2.drawString(this.name,
		    (int) (LEFT_M - 0.5 * g2.getFontMetrics().stringWidth(this.name) + 0.5 * (this.getWidth() - LEFT_M - RIGHT_M)),
		    (int) (this.getHeight() - 3.0));
	    // ---

	    // Draw plot rectangle
	    g2.draw(new Rectangle2D.Double(LEFT_M, TOP_M, this.getWidth() - LEFT_M - RIGHT_M, this.getHeight() - TOP_M - BOTTOM_M));
	    // ---

	    // Magic quit :)
	    if (this.data.isEmpty()) {
		return;
	    }
	    
	    double maxX = this.maxXValue(), minX = this.minXValue();
	    double maxY = this.maxYValue(), minY = this.minYValue();
	    double scaleX = (this.getWidth() - LEFT_M - RIGHT_M)  / (maxX - minX);
	    double scaleY = (this.getHeight() - TOP_M - BOTTOM_M) / (maxY - minY);

	    // Draw plot ticks
	    for (int tickCount=0; tickCount <= TICKS_NUM; tickCount++) {
		String tickLabel;
		int tickLabelWidth, tickLabelHeight;

		// X tick
		g2.drawLine(
			(int) (LEFT_M + tickCount * scaleX * (maxX - minX) / TICKS_NUM),
			(int) (this.getHeight() - BOTTOM_M),
			(int) (LEFT_M + tickCount * scaleX * (maxX - minX) / TICKS_NUM),
			(int) (this.getHeight() - BOTTOM_M - TICK_LEN));
		tickLabel = String.format("%.2f", new Double(tickCount * (maxX - minX) / TICKS_NUM + minX));
		tickLabelWidth  = g2.getFontMetrics().stringWidth(tickLabel);
		tickLabelHeight = g2.getFontMetrics().getHeight();
		g2.drawString(
			tickLabel,
			(int) (LEFT_M + tickCount * scaleX * (maxX - minX) / TICKS_NUM - 0.5 * tickLabelWidth),
			(int) (this.getHeight() - BOTTOM_M + tickLabelHeight));
		// Y tick
		g2.drawLine(
			(int) (LEFT_M),
			(int) (this.getHeight() - BOTTOM_M - tickCount * scaleY * (maxY - minY) / TICKS_NUM),
			(int) (LEFT_M + TICK_LEN),
			(int) (this.getHeight() - BOTTOM_M - tickCount * scaleY * (maxY - minY) / TICKS_NUM));
		tickLabel = String.format("%.2f", new Double(tickCount * (maxY - minY) / TICKS_NUM  + minY));
		tickLabelWidth  = g2.getFontMetrics().stringWidth(tickLabel);
		tickLabelHeight = g2.getFontMetrics().getHeight();
		g2.drawString(
			tickLabel,
			(int) (LEFT_M - tickLabelWidth - 3.0),
			(int) (this.getHeight() - BOTTOM_M - tickCount * scaleY * (maxY - minY) / TICKS_NUM + 0.5 * tickLabelHeight));
	    }
	    // ---

	    // Draw plot line
	    Path2D plotLine = new Path2D.Double();
	    ListIterator<? extends Point2D.Double> plotIterator = this.data.listIterator();
	    Point2D.Double firstPoint = plotIterator.next();
	    plotLine.moveTo(LEFT_M + scaleX * (firstPoint.x - minX), this.getHeight() - BOTTOM_M - scaleY *(firstPoint.y - minY));
	    while (plotIterator.hasNext()) {
		Point2D.Double currentPoint = plotIterator.next();
		plotLine.lineTo(LEFT_M + scaleX * (currentPoint.x - minX), this.getHeight() - BOTTOM_M - scaleY *(currentPoint.y - minY));
	    }
	    g2.setColor(this.lineColor);
	    g2.draw(plotLine);
	    // ---
	}

	protected double maxXValue() {
	    double result = Double.MIN_VALUE;
	    for (Point2D.Double point : this.data) {
		result = (point.x > result) ? point.x : result;
	    }
	    return result;
	}

	protected double minXValue() {
	    double result = Double.MAX_VALUE;
	    for (Point2D.Double point : this.data) {
		result = (point.x < result) ? point.x : result;
	    }
	    return result;
	}

	protected double maxYValue() {
	    double result = Double.MIN_VALUE;
	    for (Point2D.Double point : this.data) {
		result = (point.y > result) ? point.y : result;
	    }
	    return result;
	}

	protected double minYValue() {
	    double result = Double.MAX_VALUE;
	    for (Point2D.Double point : this.data) {
		result = (point.y < result) ? point.y : result;
	    }
	    return result;
	}

	private static final double LEFT_M    = 40.0;
	private static final double RIGHT_M   = 40.0;
	private static final double TOP_M     = 15.0;
	private static final double BOTTOM_M  = 40.0;

	private static final double TICK_LEN  = 10.0;
	private static final int    TICKS_NUM = 4;

	protected String name;
	protected ArrayList<? extends Point2D.Double> data;
	protected Color lineColor;
    }

    public static class PhasePoint {

	public PhasePoint(double v, double tetta, double t) {
	    this.v = v;
	    this.tetta = tetta;
	    this.t = t;
	}

	@Override
	public boolean equals(Object other) {
	    if (other instanceof PhasePoint) {
		PhasePoint ppOther = (PhasePoint) other;
		return (this.t == ppOther.t) && (this.tetta == ppOther.tetta) && (this.v == ppOther.v);
	    }
	    return false;
	}

	@Override
	public String toString() {
	    return Double.toString(t) + "\t" + Double.toString(v) + "\t" + Double.toString(tetta);
	}

	public double getV() {
	    return this.v;
	}

	public double getTetta() {
	    return this.tetta;
	}

	public double getT() {
	    return this.t;
	}

	private double v;
	private double tetta;
	private double t;
    }
    
    protected ArrayList<PhasePoint> phaseSpace;
    protected ArrayList<Point2D.Double> track;
    
    public void addTrack(Point2D.Double point) {
	this.track.add(point);
    }
    
    public void clearTrack() {
	this.track.clear();
    }
    
    public ArrayList<Point2D.Double> getTrack() {
	return this.track;
    }
    
    protected volatile ArrayList<Point2D.Double> plotVT;
    protected volatile ArrayList<Point2D.Double> plotTettaT;
    protected volatile ArrayList<Point2D.Double> plotTettaV;
    
    protected volatile PlotPanel plotPanelVT;
    protected volatile PlotPanel plotPanelTettaT;
    protected volatile PlotPanel plotPanelTettaV;
    protected volatile PlotPanel plotPanelTrack;
}
